﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using AGB.IDemonbuddy;
using AGB.Managerial;
using Zeta;
using Zeta.Common;
using Zeta.CommonBot;
using Zeta.CommonBot.Settings;
using Zeta.Internals;
using Zeta.Internals.Actors;
using Zeta.Navigation;
using Zeta.TreeSharp;
using Action = Zeta.TreeSharp.Action;

namespace AGB.Modules.FrontalLobe_.VendorRun
{
    class Vendoring : PrioritySelector
    {
        public enum Steps
        {
            PortHome,

            Identify,

            MoveToStash,
            InteractStash,
            Stash,

            MoveToRepair,
            TalkRepairman,
            Repair,

            MoveToSmithy,
            InteractSmithy,
            Salvage,

            MoveToStash2,
            InteractStash2,
            Stash2,

            MoveToPortal,
            TakePortal,
 
            Finnish
        }

        private Steps myStep;  

        private RunStatus TalkToRepairman(object context)
        {
            if (RepairWindowIsVisible())
            {
                myStep = Steps.Repair;
                return RunStatus.Failure;
            }
            var rep = BotManager.UnitManager.GetObject<CachedUnit>(getRepairmanName(null));
            rep.Interact();
            return RunStatus.Success;
        }


        private RunStatus InteractWithSmithy(object context)
        {
            if (SalvageWindowIsVisible())
            {
                myStep = Steps.Salvage;
                return RunStatus.Failure;
            }
            var rep = BotManager.UnitManager.GetObject<CachedUnit>(getSmithyName(null));
            rep.Interact();
            return RunStatus.Success;
        }

        private RunStatus InteractWithStash(object context)
        {
            if (StashWindowIsVisible())
            {
                myStep = Steps.Stash;
                return RunStatus.Failure;
            }
            var rep = BotManager.UnitManager.GetObject<CachedGizmo>(getStashName(null));
            rep.Interact();
            return RunStatus.Success;
        }

        private RunStatus InteractWithStash2(object context)
        {
            if (StashWindowIsVisible())
            {
                myStep = Steps.Stash2;
                return RunStatus.Failure;
            }
            var rep = BotManager.UnitManager.GetObject<CachedGizmo>(getStashName(null));
            rep.Interact();
            return RunStatus.Success;
        }

        public bool ShouldStash()
        {
            foreach (var items in ZetaDia.Me.Inventory.Backpack)
            {
                bool result =
                    ItemManager.ShouldStashItem(
                        items);
                if (result) return true;

            }
            return false;
        }

        public bool ShouldSellOrRepair()
        {
            if (ZetaWrap.NeedsRepair())
                return true;

            foreach (var items in ZetaDia.Me.Inventory.Backpack)
            {
                bool result = ItemManager.ShouldSellItem(items) && !ItemManager.ShouldStashItem(items) && !ItemManager.ShouldSalvageItem(items);
                if (result) return true;

            }
            return false;
        }

        public bool ShouldSalvage()
        {
            foreach (var items in ZetaDia.Me.Inventory.Backpack)
            {
                bool result = ItemManager.ShouldSalvageItem(items) && !ItemManager.ShouldStashItem(items);
                
                if (result) return true;

            }
            return false;
        }

        private long _lastStashAction = Environment.TickCount;
        private const long StashTimeout = 400;
        private static readonly Dictionary<int, GreylistEntry> StashingGreylist = new Dictionary<int, GreylistEntry>();


        private class GreylistEntry
        {
            public int Count = 0;
            public bool Banned = false;

        }

        private RunStatus Stash(object context)
        {
            return _stash(Steps.MoveToSmithy);

        }

        private RunStatus _stash(Steps step)
        {
            if (_lastStashAction + StashTimeout > Environment.TickCount) return RunStatus.Success;

            _lastStashAction = Environment.TickCount;


            foreach (var item in ZetaDia.Me.Inventory.Backpack)
            {
                item.ParseItemTable();
                bool result = ItemManager.ShouldStashItem(item);

                if (result)
                {
                    /*if (!_stashingGreylist.ContainsKey(item.DynamicId))
                        _stashingGreylist.Add(item.DynamicId, new GreylistEntry());

                    if (_stashingGreylist[item.DynamicId].Banned) continue;

                    if (_stashingGreylist[item.DynamicId].Count++ > 2)
                    {
                        _stashingGreylist[item.DynamicId].Banned = true;
                        Logging.WriteVerbose("Banning " + item.Name + " lets try stashing it the next townrun");
                        continue;
                    }*/

                    if (!QuickStash(item))
                    {
                        Logging.Write("Ouch we failed Stashing :/");
                        break;
                    };
                    return RunStatus.Success;
                }

            }

            Blacklist.Clear();
            myStep = step;
            return RunStatus.Failure;           
        }

        private RunStatus Stash2(object context)
        {
            return _stash(Steps.MoveToRepair);
        }

        private RunStatus SafetyLoot(object context)
        {
            if (!ZetaWrap.IsInTown()) return RunStatus.Failure;
            ZetaDia.Actors.Update();
    
            foreach (var item in ZetaDia.Actors.GetActorsOfType<DiaItem>())
            {
                item.Interact();
                return RunStatus.Success;
            }
            return RunStatus.Failure;
        }

        private long _lastSalvageTick = Environment.TickCount;
        private long _salvageTimeout = 50;

        private RunStatus Salvage(object context)
        {
            if (_lastSalvageTick + _salvageTimeout > Environment.TickCount) return RunStatus.Success;
            _lastSalvageTick = Environment.TickCount;

            ZetaDia.Actors.Update();
            ItemManager.Refresh();

            int i = 0;

            foreach (var items in ZetaDia.Me.Inventory.Backpack)
            {
                items.ParseItemTable();
                bool result = ItemManager.ShouldSalvageItem(items) && !ItemManager.ShouldStashItem(items);

                if (result)
                {
                    ZetaDia.Me.Inventory.SalvageItem(items.DynamicId);
                    i++;
                    return RunStatus.Success;
                }

            }
            if (i == 0)
            {
                myStep = Steps.MoveToStash2;
                return RunStatus.Failure;
            }

            return RunStatus.Success;
        }

        private long _takePortalTick = Environment.TickCount;
        private const long TakePortalTimer = 500;

        private RunStatus TakePortal(object context)
        {
            if (_takePortalTick + TakePortalTimer > Environment.TickCount) return RunStatus.Success;

            _takePortalTick = Environment.TickCount;

            if (!ZetaWrap.IsInTown() && !ZetaWrap.IsLoadingWorld())
            {
                myStep = Steps.Finnish;
                return RunStatus.Failure;
            }

            var portal = BotManager.UnitManager.GetObject<CachedGizmo>("hearthPortal");

            if (portal.IsValid()) portal.Interact();

            return RunStatus.Success;
        }

        private long _lastRepairTick = Environment.TickCount;
        private long _repairTimeout = 50;


        private RunStatus RepairAndSell(object context)
        {
            if (_lastRepairTick + _repairTimeout > Environment.TickCount) return RunStatus.Success;
            _lastRepairTick = Environment.TickCount;

            ZetaDia.Actors.Update();
            ItemManager.Refresh();
            ZetaDia.Me.Inventory.RepairEquippedItems();
            int i = 0;

            foreach (var items in ZetaDia.Me.Inventory.Backpack)
            {
                items.ParseItemTable();
                bool result = ItemManager.ShouldSellItem(items) && !ItemManager.ShouldStashItem(items) &&
                              !ItemManager.ShouldSalvageItem(items);

                if (result)
                {
                    ZetaDia.Me.Inventory.SellItem(items.DynamicId);
                    return RunStatus.Success;
                    i++;
                }

            }
            if (i == 0)
            {
                myStep = Steps.MoveToPortal;
                return RunStatus.Failure;
            }
            return RunStatus.Success;
        }

        private long _lastIdentifyTick = Environment.TickCount;
        private const long IdentifyTimer = 500;

        private static bool IsUnid(ACDItem i)
        {
            return i.IsUnidentified;
        }


        private RunStatus IdentifyItems(object context)
        {
            if (AGB.Properties.Settings.Default.NoIdentify)
            {
                myStep = Steps.MoveToStash;
                return RunStatus.Failure;
            }
            if (_lastIdentifyTick + IdentifyTimer > Environment.TickCount) return RunStatus.Success;

            _lastIdentifyTick = Environment.TickCount;

            var item = ZetaDia.Me.Inventory.Backpack.FirstOrDefault<ACDItem>(IsUnid);

            if (item == default(ACDItem))
            {
                myStep = Steps.MoveToStash;
                return RunStatus.Failure;
            }

            ZetaDia.Me.Inventory.IdentifyItem(item.DynamicId);

            return RunStatus.Success;
        }

        private bool _portalFlag = false;

        public Vendoring()
        {
            Blacklist.Clear();
            
            myStep = Steps.PortHome;
            AddChild(new Decorator(rem => myStep == Steps.PortHome,PortHome()));

            AddChild(new Decorator(rem => myStep == Steps.Identify, new Action((ActionDelegate)IdentifyItems)));

            AddChild(new Decorator(rem => myStep == Steps.MoveToStash, MoveTo(getStash(null), Steps.InteractStash)));
            AddChild(new Decorator(rem => myStep == Steps.InteractStash, new Action((ActionDelegate)InteractWithStash)));
            AddChild(new Decorator(rem => myStep == Steps.Stash, new Action((ActionDelegate)Stash)));

            AddChild(new Decorator(rem => myStep == Steps.MoveToSmithy, MoveTo(GetSmithy(null), Steps.InteractSmithy)));
            AddChild(new Decorator(rem => myStep == Steps.InteractSmithy, new Action((ActionDelegate)InteractWithSmithy)));
            AddChild(new Decorator(rem => myStep == Steps.Salvage, new Action((ActionDelegate)Salvage)));

            AddChild(new Decorator(rem => myStep == Steps.MoveToStash2, MoveTo(getStash(null), Steps.InteractStash2)));
            AddChild(new Decorator(rem => myStep == Steps.InteractStash2, new Action((ActionDelegate)InteractWithStash2)));
            AddChild(new Decorator(rem => myStep == Steps.Stash2, new Action((ActionDelegate)Stash2)));

            AddChild(new Decorator(rem => myStep == Steps.MoveToRepair, MoveTo(getRepairman(null), Steps.TalkRepairman)));
            AddChild(new Decorator(rem => myStep == Steps.TalkRepairman, new Action((ActionDelegate)TalkToRepairman)));
            AddChild(new Decorator(rem => myStep == Steps.Repair, new Action((ActionDelegate)RepairAndSell)));

            AddChild(new Decorator(rem => myStep == Steps.MoveToPortal, MoveTo(getPortal(null), Steps.TakePortal)));
            AddChild(new Decorator(rem => myStep == Steps.TakePortal,new Action((ActionDelegate)TakePortal)));
        }

        private long _lastPortalTick = Environment.TickCount;
        private const long PortalTimer = 500;


        public Action PortHome()
        {
            return new Action(delegate(object context)
                                  {
                                      IsVendoring = true;
                                      if (_lastPortalTick + PortalTimer > Environment.TickCount) return RunStatus.Success;

                _lastPortalTick = Environment.TickCount;


                
                if (ZetaWrap.IsInTown())
                {

                    String reason = "";
                    reason += ZetaWrap.ForceVendoring ? "| Inventory Full! |" : "";
                    reason += ZetaWrap.NeedsRepair() ? "| We need Repair! |" : "";
                    Logging.Write("Starting Townrun: {"+reason+"}");

                    myStep = Steps.Identify;
                    return RunStatus.Failure;
                }

                _portalFlag = true;

                if (ZetaDia.Me.CommonData.AnimationState == AnimationState.Channeling) return RunStatus.Success;

                ZetaDia.Me.UseTownPortal();

                return RunStatus.Success;
            })
            ;
        }

        public static bool IsTwoSlot(ACDItem i)
        {
            i.ParseItemTable();
            string iName = i.InternalName;

            if (
                iName.Contains("Belt_") ||
                iName.Contains("Crafting_") ||
                iName.Contains("Belt_") ||
                iName.Contains("Ring_") ||
                iName.Contains("Amulet_") ||
                iName.Contains("Ruby_") ||
                iName.Contains("Emerald_") ||
                iName.Contains("Topaz_") ||
                iName.Contains("Amethyst") ||
                iName.Contains("FollowerItem_") ||
                iName.Contains("A1_") ||
                iName.Contains("Cow_") ||
                iName.Contains("FollowerItem_") ||
                iName.Contains("CraftingPlan_") ||
                iName.Contains("healthPotion_") ||
                iName.Contains("Dye")
                ) return false;

            /*if (iName.Contains("Quiver_") || iName.Contains("Mojo_") || iName.Contains("orb_")) return true;

            if (i.Stats.Armor > 0)
                return true;

            if (i.Stats.WeaponDamagePerSecond > 0) return true;*/

            return true;
            //return false;
        }

        private static int[,] stash = new int[7, 30];

        public static void DrawStash()
        {
            Bitmap inventory = new Bitmap(4096, 2048);
            Graphics g = Graphics.FromImage(inventory);

            FontFamily myFontFamily = new FontFamily("Arial");
            Font myFont = new Font(myFontFamily,
               16,
               FontStyle.Regular,
               GraphicsUnit.Pixel);

            //g.DrawRectangle(new Pen(Color.Black),0,0,1000,600);

            int sqSize = 100;
            int sqhalf = sqSize / 2;

            for (int x = 0; x < 7; x++ )
            {
                for (int y = 0; y < 30; y++)
                {

                    
                    int row = y;
                    int col = x;

                    Brush myBrush = new SolidBrush(Color.Green);

                    if (stash[x, y] == 1) myBrush = new SolidBrush(Color.Red);
                    if (stash[x, y] == 2) myBrush = new SolidBrush(Color.Blue);

                    if (row > 9 && row <= 19)
                    {
                        col = col + 7;
                        row = row - 10;
                    }

                    if (row > 19 && row <= 29)
                    {
                        col = col + 14;
                        row = row - 20;
                    }
                    //var textSize = g.MeasureString(text, myFont);
                    g.FillRectangle(myBrush, col * sqSize, row * sqSize, sqSize, sqSize);
                    g.DrawRectangle(new Pen(Color.Black), col * sqSize, row * sqSize, sqSize, sqSize);
                    //g.DrawString(text, myFont, new SolidBrush(Color.Red), (col * sqSize) + sqhalf - textSize.Width / 2, (row * sqSize) + sqhalf - textSize.Height / 2);
                }
            }

            inventory.Save("Inv.bmp");
       
        }

        public static int GetNumBackpackSlots()
        {
            ZetaDia.Actors.Update();
            ItemManager.Refresh();

            int size = 60;
            foreach (var item in ZetaDia.Me.Inventory.Backpack)
            {
                item.ParseItemTable();
                size -= IsTwoSlot(item) ? 2 : 1;
            }
            return size;
        }

        public static void ResetStash()
        {

            stash = new int[7, 30];

            foreach (var item in ZetaDia.Me.Inventory.StashItems)
            {
                item.ParseItemTable();

                int row = item.InventoryRow;
                int col = item.InventoryColumn;

                stash[col, row] = 1;

                //Logging.Write(item.Name+" "+item.InternalName+" "+IsTwoSlot(item));
                
                if (IsTwoSlot(item))
                {
                    if (row+1 < 30)stash[col, (row+1)] = 1;
                    
                }
            }

            foreach (var i in Zeta.CommonBot.Settings.CharacterSettings.Instance.ProtectedStashPages)
            {
                int pMod = i * 10;
                for (int k = pMod; k < pMod + 10; k++)
                {
                    for (int j = 0; j < 7; j++)
                    {
                        stash[j, k] = 2;
                    }
                }
            }

            /*foreach (var i in Blacklist)
            {
                if (i.Count > 1)
                    stash[i.X, i.Y] = 2;
            }*/


        }

        public Point FindHole()
        {
            //Logging.Write("Finding hole");
            for (int y = 0; y < 30; y++)
            {
                for (int x = 0; x < 7; x++)
                {
                    if (stash[x, y] > 0) continue;

                    int score = 0;

                    if (y == 0 || y == 10 || y == 20) score++; else if (stash[x, y - 1] > 0) score++;
                    if (y == 9 || y == 19 || y == 29) score++; else if (stash[x, y + 1] > 0) score++;

                    if (score == 2) return new Point(x, y);

                }
            }
            return new Point(-1, -1);
        }

        public Point FindSingle()
        {
            //Logging.Write("Finding Single");
            for (int y = 0; y < 30; y++)
            {
                for (int x = 0; x < 7; x++)
                {
                    if (stash[x, y] > 0) continue;
                    return new Point(x, y);
                }
            }
            return new Point(-1, -1);
        }

        public Point FindStack(ACDItem i)
        {
            foreach (var item in ZetaDia.Me.Inventory.StashItems)
            {
                if ((item.GameBalanceId == i.GameBalanceId) && (item.ItemStackQuantity < item.MaxStackCount))
                {
                    return new Point(item.InventoryColumn,item.InventoryRow);
                }

            }
            return new Point(-1,-1);
        }

        public Point FindDouble()
        {
            //Logging.Write("Finding Double");
            for (int y = 0; y < 30; y++)
            {
                for (int x = 0; x < 7; x++)
                {
                    if (stash[x, y] > 0) continue;

                    int score = 0;

                    if (y == 9 || y == 19 || y == 29) score++; else if (stash[x, y + 1] > 0) score++;
                    
                    if (score == 0) return new Point(x, y);

                }
            }
            return new Point(-1, -1);
        }

        private class Blacklistee
        {
            public readonly int X;
            public readonly int Y;
            public int Count;

            public Blacklistee(int x, int y)
            {
                Count = 0;
                X = x;
                Y = y;
            }
            public override bool Equals(object obj)
            {
                if (!(obj is Blacklistee)) return false;               
                var x = (Blacklistee)obj;
                return x.Y == Y && x.X == X;
            }
            public override int GetHashCode()
            {
                return (Y*30 + X);
            }
            public override string ToString()
            {
                return "(" + X + "/" + Y + ")";
            }

        }

        private static List<Blacklistee> Blacklist = new List<Blacklistee>(); 

        private static bool BlacklistSlot(int x, int y)
        {
            var p = new Blacklistee(x, y);
            if (!Blacklist.Contains(p)) Blacklist.Add(p); else p = Blacklist.Find(ra=> ra.Equals(p));
            p.Count++;
            if (p.Count > 2) return true;
            return false;
        }

        public bool QuickStash(ACDItem item)
        {

            //ZetaDia.Me.Inventory.IdentifyItem(ZetaDia.Me.Inventory.Backpack.First<ACDItem>(func_2).DynamicId

            ResetStash();

            Point slot;

            slot = FindStack(item);

            if (slot.X >= 0 && slot.Y >= 0)
            {
                ZetaDia.Me.Inventory.MoveItem(item.DynamicId, ZetaDia.Me.CommonData.DynamicId, InventorySlot.PlayerSharedStash, slot.X, slot.Y);
                return true;
            }

            if (IsTwoSlot(item)) slot = FindDouble();
            else
            {
                slot = FindHole();
                if (slot.X < 0 && slot.Y < 0) slot = FindSingle();
            }

            if (slot.X < 0 || slot.Y < 0) return false;

            Logging.WriteVerbose("Stashing " + item.Name + " @ " + slot.X + "/" + slot.Y);

            BlacklistSlot(slot.X, slot.Y);

            ZetaDia.Me.Inventory.MoveItem(item.DynamicId, ZetaDia.Me.CommonData.DynamicId, InventorySlot.PlayerSharedStash, slot.X, slot.Y);

            return true;
        }

        public Action MoveTo(Vector3 dest, Steps newStep)
        {
            return new Action(delegate(object context)
            {
                if (!ZetaWrap.IsInTown())
                {
                    
                    Logging.Write("Not in town and moving");
                    myStep = Steps.PortHome;
                    IsVendoring = false;
                    return RunStatus.Failure;
                }
                if (myStep == Steps.MoveToStash && !ShouldStash())
                {
                    myStep = Steps.MoveToSmithy;
                    return RunStatus.Failure;
                }

                if (myStep == Steps.MoveToRepair && !ShouldSellOrRepair()) 
                {
                     myStep = Steps.MoveToPortal;
                    return RunStatus.Failure;
                }

                if (myStep == Steps.MoveToSmithy && !ShouldSalvage())
                {
                    myStep = Steps.MoveToRepair;
                    return RunStatus.Failure;
                }

                if (myStep == Steps.MoveToPortal && !_portalFlag)
                {
                    myStep = Steps.Finnish;
                    return RunStatus.Failure;
                }

                switch (Navigator.MoveTo(dest))
                {
                    case MoveResult.ReachedDestination:
                        myStep = newStep;
                        return RunStatus.Failure;
                }
                return RunStatus.Success;
            })
                           ;
        }

        public Vector3 GetSmithy(object context)
        {
            return (new Vector3(2941.15f, 2853.651f, 24.04533f));
        }

        public Vector3 getRepairman(object context)
        {
            return (new Vector3(2895.571f, 2785.318f, 24.04532f));
            //return (new Vector3(3032.838f, 2728.284f, 24.04532f));
        }

        public Vector3 getStash(object context)
        {
            return (new Vector3(2972.006f, 2796.15f, 24.04533f));
        }

        public Vector3 getPortal(object context)
        {
            return (new Vector3(2984.02f, 2797.653f, 24.04533f));
        }

        public String getRepairmanName(object context)
        {
            return "A1_UniqueVendor_Miner_InTown";
            //return "A1_UniqueVendor_Collector_InTown";
        }

        public String getStashName(object context)
        {
            return "Player_Shared_Stash";
        }

        public String getSmithyName(object context)
        {
            return "PT_Blacksmith_RepairShortcut";
        }

        public bool StashWindowIsVisible()
        {
            //0xB83F0423F7247928
            if (UIElement.IsValidElement(0xB83F0423F7247928)) if (UIElement.FromHash(0xB83F0423F7247928).IsVisible)
                return true;
            return false;
        }

        public bool RepairWindowIsVisible()
        {
            //0xA83F2BC15AC524D7
            if (UIElement.IsValidElement(0xA83F2BC15AC524D7)) if (UIElement.FromHash(0xA83F2BC15AC524D7).IsVisible)
                    return true;
            return false;
        }

        public bool SalvageWindowIsVisible()
        {
            //0x359867FD497D2FF3
            if (UIElement.IsValidElement(0x359867FD497D2FF3)) if (UIElement.FromHash(0x359867FD497D2FF3).IsVisible)
                    return true;
            return false;
        }

        public long _lastTP = Environment.TickCount;
        public const long TPTimer = 400;

        public Composite GenerateTownPortalBehavior()
        {
            CanRunDecoratorDelegate InTown = delegate
                                                 {
                                                     bool timeOut = (_lastTP + TPTimer < Environment.TickCount);
                if (ZetaDia.Me == null) return false;
                if (!ZetaWrap.IsInTown() && (ZetaDia.Me.CommonData.AnimationState != AnimationState.Channeling || timeOut))
                {
                    _lastTP = Environment.TickCount;
                    return true;
                }
                return false;
            };

            var pSequence = new Sequence();
            var PortHome = new Action(delegate
            {
                ZetaDia.Me.UseTownPortal();
                return RunStatus.Success;
            });
            pSequence.AddChild(PortHome);
            return new DecoratorContinue(InTown, PortHome);
        }

        public int VendorTicks = 0;

        public override RunStatus Tick(object context)
        {
            RunStatus result = base.Tick(context);

            if (!ZetaWrap.IsInGame() || ZetaWrap.IsDead())
            {
                IsVendoring = false;
                Logging.Write("Were dead in Town, the fuck ?");
                return RunStatus.Failure;
            }

            if (myStep == Steps.Finnish)
            {
                //Logging.Write("Finnished Townrun");
                VendorTicks = 0;
                ZetaWrap.ForceVendoring = false;
                BotManager.CerebralCortex.ClearBlacklist();
                IsVendoring = false;
                CharacterSettings.Instance.RepairWhenDurabilityBelow = 10;
                myStep = Steps.PortHome;
                
                return RunStatus.Failure;
            }
            if (VendorTicks < 30) BotManager.CerebralCortex.WatchdogExtension += 1000;
            return RunStatus.Success;
        }

        public static bool IsVendoring;

    }
}
